part of flutter_date_picker;

typedef DateChangedCallback(DateTime time);
typedef CancelCallback();
typedef String? StringAtIndexCallBack(int index);

final padding = EdgeInsets.all(6.0);
final headerHeight = 48;
final startTime = DateTime(1970); // DateTime.parse('2018-05-16 08:21');
final endTime = DateTime(DateTime
    .now()
    .year + 50); // DateTime.parse('2022-05-06 09:21');

/// current, start, end 传入时间字符串或者DateTime均可
///
class DateTimePicker extends StatefulWidget {
  final DateTime now = new DateTime.now();

  final double itemHeight;
  final DateTime current;
  final DateTime? start;
  final DateTime? end;
  final DateChangedCallback? onChange;
  final DateChangedCallback? onConfirm;
  final CancelCallback? onCancel;
  final DatePickerMode mode;
  final bool isShowHeader;
  final double magnification;
  final double offAxisFraction;
  final double squeeze;
  final Color? color;
  final Color? background;
  final Widget? header;
  final Widget? title;
  final String hithertoString;

  DateTimePicker({current,
    start,
    end,
    this.itemHeight = 36,
    this.magnification = 1.2,
    this.offAxisFraction = 0.2,
    this.squeeze = 1.45,
    this.onChange,
    this.onConfirm,
    this.onCancel,
    this.color,
    this.background,
    isShowHeader = true,
    this.header = null,
    this.title = null,
    this.mode = DatePickerMode.date,
    this.hithertoString = '至今'})
      : this.current = mode == DatePickerMode.hitherto
      ? PickerDate.getNow()
      : current != null
      ? PickerDate.parse(current)
      : PickerDate.getNow(),
        this.start = start != null ? PickerDate.parse(start) : startTime,
        this.end = mode == DatePickerMode.hitherto
            ? PickerDate.getNow()
            : end != null
            ? PickerDate.parse(end)
            : endTime,
        this.isShowHeader = isShowHeader ?? true;

  get pickerHeight => itemHeight * 5 + padding.top + padding.bottom;

  get hideYear => mode == DatePickerMode.time;

  get hideMonth => mode == DatePickerMode.year || mode == DatePickerMode.time;

  get hideDay => mode == DatePickerMode.year || mode == DatePickerMode.month || mode == DatePickerMode.time;

  get hideTime =>
      mode == DatePickerMode.year ||
          mode == DatePickerMode.month ||
          mode == DatePickerMode.date ||
          mode == DatePickerMode.dateHalf ||
          mode == DatePickerMode.hitherto;

  get hideSecond => mode != DatePickerMode.full;

  get hideHalf =>
      mode == DatePickerMode.year ||
          mode == DatePickerMode.month ||
          mode == DatePickerMode.date ||
          mode == DatePickerMode.time ||
          mode == DatePickerMode.full ||
          mode == DatePickerMode.hitherto ||
          mode == DatePickerMode.dateTime;

  @override
  _DateTimePickerState createState() => _DateTimePickerState();
}

class _DateTimePickerState extends State<DateTimePicker> {
  final DateTime now = new DateTime.now();
  int yearIndex = 0;
  int monthIndex = 0;
  int dayIndex = 0;
  int hourIndex = 0;
  int minuteIndex = 0;
  int secondIndex = 0;
  int halfIndex = 0;

  /// 上一次回调的时间
  DateTime? _last;

  // 如果是手动滚动，不要触发onChange
  bool _isScroll = false;

  /// 定时器
  Timer? timer;

  late FixedExtentScrollController yearScrollCtrl, monthScrollCtrl, dayScrollCtrl, hourScrollCtrl, minuteScrollCtrl, secondScrollCtrl, halfScrollCtrl;

  get _realDate => new DateTime(now.year + yearIndex, monthIndex + 1, dayIndex + 1, hourIndex, minuteIndex, secondIndex);

  get days => PickerDate.daysInMonth(_realDate);

  int getDays() {
    DateTime date = new DateTime(now.year + yearIndex, monthIndex + 1);
    return PickerDate.daysInMonth(date);
  }

  @override
  void initState() {
    super.initState();
    refreshScrollOffset();
  }

  @override
  void dispose() {
    hourScrollCtrl.dispose();
    minuteScrollCtrl.dispose();
    secondScrollCtrl.dispose();
    dayScrollCtrl.dispose();
    yearScrollCtrl.dispose();
    monthScrollCtrl.dispose();
    halfScrollCtrl.dispose();
    super.dispose();
  }

  void refreshScrollOffset() {
    yearIndex = widget.current.year - now.year;
    monthIndex = widget.current.month - 1;
    dayIndex = widget.current.day - 1;
    hourIndex = widget.current.hour;
    minuteIndex = widget.current.minute;
    secondIndex = widget.current.second;

    if (widget.mode == DatePickerMode.dateHalf) {
      hourIndex = halfIndex == 0 ? 6 : 12;
    }

    yearScrollCtrl = new FixedExtentScrollController(initialItem: yearIndex);
    monthScrollCtrl = new FixedExtentScrollController(initialItem: monthIndex);
    dayScrollCtrl = new FixedExtentScrollController(initialItem: dayIndex);
    hourScrollCtrl = new FixedExtentScrollController(initialItem: hourIndex);
    minuteScrollCtrl = new FixedExtentScrollController(initialItem: minuteIndex);
    secondScrollCtrl = new FixedExtentScrollController(initialItem: secondIndex);
    halfScrollCtrl = new FixedExtentScrollController(initialItem: halfIndex);
  }

  changeYear(index) {
    if (index == yearIndex) return;


    int max = widget.end!.year - widget.current!.year;
    if (widget.mode == DatePickerMode.hitherto) {
      max += 1;
    }
    int min = widget.start!.year - widget.current!.year;
    if (index < min) {
      yearIndex = min;
      yearScrollCtrl.jumpToItem(yearIndex);
      setState(() {});
    } else if (index > max) {
      yearIndex = max;
      yearScrollCtrl.jumpToItem(yearIndex);
      setState(() {});
    } else {
      yearIndex = index;
      setState(() {});
    }
    if (!(widget.mode == DatePickerMode.year || widget.mode == DatePickerMode.time)) {
      setMonthCtrl();
    }
  }

  String? stringIndexByYear(int index) {
    if (index >= -100 && index <= 100) {
      if (widget.mode == DatePickerMode.hitherto && index > 0) {
        if (index == 1) {
          return widget.hithertoString;
        }
      }
      int _year = now.year + index;
      DateTime _nowDate = DateTime(_year);
      DateTime? start = widget.start == null ? null : DateTime(widget.start!.year);
      DateTime? end = widget.end == null ? null : DateTime(widget.end!.year);

      return PickerDate.isInRange(_nowDate, start, end) ? _year.toString() + '年' : null;
    }
    return null;
  }

  changeMonth(index) {
    if (index == monthIndex) return;

    int max = 11;
    int min = 0;

    if (widget.end != null) {
      max = now.year + yearIndex == widget.end!.year ? (widget.end!.month - 1) : 11;
    }
    if (widget.start != null) {
      min = now.year + yearIndex == widget.start!.year ? (widget.start!.month - 1) : 0;
    }
    if (index < min) {
      monthIndex = min;
      monthScrollCtrl.jumpToItem(monthIndex);
      setState(() {});
    } else if (index > max) {
      monthIndex = max;
      monthScrollCtrl.jumpToItem(monthIndex);
      setState(() {});
    } else {
      monthIndex = index;
      setState(() {});
    }

    if (!(widget.mode == DatePickerMode.year || widget.mode == DatePickerMode.month || widget.mode == DatePickerMode.time)) {
      setDayCtrl();
    }
  }

  String? stringIndexByMonth(int index) {
    if (index >= 0 && index <= 11) {
      int _monthIndex = index + 1;
      DateTime _nowDate = DateTime(now.year + yearIndex, _monthIndex);
      DateTime? start = widget.start == null ? null : DateTime(widget.start!.year, widget.start!.month);
      DateTime? end = widget.end == null ? null : DateTime(widget.end!.year, widget.end!.month);

      return PickerDate.isInRange(_nowDate, start, end) ? (index + 1).toString() + '月' : null;
    }
    return null;
  }

  setMonthCtrl() {
    int max = 11;
    int min = 0;

    if (widget.end != null) {
      max = now.year + yearIndex == widget.end!.year ? (widget.end!.month - 1) : 11;
    }
    if (widget.start != null) {
      min = now.year + yearIndex == widget.start!.year ? (widget.start!.month - 1) : 0;
    }

    scrollItem(monthIndex, monthScrollCtrl, min, max);
  }

  setScroll(fn) {
    _isScroll = true;
    fn();
    _isScroll = false;
  }

  setDayCtrl() {
    int days = getDays();
    int max = days - 1;
    int min = 0;

    if (widget.end != null) {
      max = (now.year + yearIndex == widget.end!.year && monthIndex == (widget.end!.month - 1)) ? (widget.end!.day - 1) : max;
    }
    if (widget.start != null) {
      min = (now.year + yearIndex == widget.start!.year && monthIndex == (widget.start!.month - 1)) ? (widget.start!.day - 1) : min;
    }
    scrollItem(dayIndex, dayScrollCtrl, min, max);
  }

  scrollItem(int index, FixedExtentScrollController ctrl, int min, int max) {
    if (index > max) {
      setScroll(() {
        ctrl.jumpToItem(max);
      });
    } else if (index < min) {
      setScroll(() {
        ctrl.jumpToItem(min);
      });
    } else {
      setScroll(() {
        // ctrl.jumpToItem(index - 1);
        ctrl.jumpToItem(index);
      });
    }
  }

  changeDay(index) {
    if (index == dayIndex) return;
    int days = getDays();
    int max = days - 1;
    int min = 0;

    if (widget.end != null) {
      max = (now.year + yearIndex == widget.end!.year && monthIndex == (widget.end!.month - 1)) ? (widget.end!.day - 1) : max;
    }
    if (widget.start != null) {
      min = (now.year + yearIndex == widget.start!.year && monthIndex == (widget.start!.month - 1)) ? (widget.start!.day - 1) : min;
    }

    if (index < min) {
      dayIndex = min;
      dayScrollCtrl.jumpToItem(dayIndex);
      setState(() {});
    } else if (index > max) {
      dayIndex = max;
      dayScrollCtrl.jumpToItem(dayIndex);
      setState(() {});
    } else {
      dayIndex = index;
      setState(() {

      });
    }

    if (widget.mode == DatePickerMode.dateTime) {
      setHourCtrl();
    }
  }

  setHourCtrl() {
    int max = 23;
    int min = 0;

    if (widget.end != null) {
      max = (now.year + yearIndex == widget.end!.year && monthIndex == (widget.end!.month - 1) && dayIndex == (widget.end!.day - 1))
          ? widget.end!.hour
          : max;
    }
    if (widget.start != null) {
      min = (now.year + yearIndex == widget.start!.year && monthIndex == (widget.start!.month - 1) && dayIndex == (widget.start!.day - 1))
          ? widget.start!.hour
          : min;
    }

    scrollItem(hourIndex, hourScrollCtrl, min, max);
  }

  String? stringIndexByDay(int index) {
    int days = getDays();

    if (index >= 0 && index < days) {
      int _index = index + 1;
      DateTime _nowDate = DateTime(now.year + yearIndex, monthIndex + 1, _index);
      DateTime? start = widget.start == null ? null : DateTime(widget.start!.year, widget.start!.month, widget.start!.day);
      DateTime? end = widget.end == null ? null : DateTime(widget.end!.year, widget.end!.month, widget.end!.day);

      return PickerDate.isInRange(_nowDate, start, end) ? _index.toString() + '日' : null;
    }
    return null;
  }

  changeHour(index) {
    if (index == hourIndex) return;

    setState(() {
      hourIndex = index;
    });
    if (widget.mode == DatePickerMode.dateTime || widget.mode == DatePickerMode.time) {
      setMinuteCtrl();
    }
    if (widget.mode == DatePickerMode.full) {
      setSecondCtrl();
    }
  }

  setMinuteCtrl() {
    int max = 59;
    int min = 0;

    if (widget.end != null) {
      max = (now.year + yearIndex == widget.end!.year &&
          monthIndex == (widget.end!.month - 1) &&
          dayIndex == (widget.end!.day - 1) &&
          hourIndex == (widget.end!.hour))
          ? widget.end!.minute
          : max;
    }
    if (widget.start != null) {
      min = (now.year + yearIndex == widget.start!.year &&
          monthIndex == (widget.start!.month - 1) &&
          dayIndex == (widget.start!.day - 1) &&
          hourIndex == (widget.start!.hour))
          ? widget.start!.minute
          : min;
    }

    scrollItem(minuteIndex, minuteScrollCtrl, min, max);
  }

  setSecondCtrl() {
    int max = 59;
    int min = 0;

    if (widget.end != null) {
      max = (now.year + yearIndex == widget.end!.year &&
          monthIndex == (widget.end!.month - 1) &&
          dayIndex == (widget.end!.day - 1) &&
          hourIndex == (widget.end!.hour) &&
          minuteIndex == (widget.end!.minute))
          ? widget.end!.second
          : max;
    }
    if (widget.start != null) {
      min = (now.year + yearIndex == widget.start!.year &&
          monthIndex == (widget.start!.month - 1) &&
          dayIndex == (widget.start!.day - 1) &&
          hourIndex == (widget.start!.hour) &&
          minuteIndex == (widget.start!.minute))
          ? widget.start!.second
          : min;
    }

    scrollItem(secondIndex, secondScrollCtrl, min, max);
  }

  String? stringIndexByHour(int index) {
    if (index >= 0 && index < 24) {
      DateTime _nowDate = DateTime(now.year + yearIndex, monthIndex + 1, dayIndex + 1, index);
      DateTime? start = widget.start == null ? null : DateTime(widget.start!.year, widget.start!.month, widget.start!.day, widget.start!.hour);
      DateTime? end = widget.end == null ? null : DateTime(widget.end!.year, widget.end!.month, widget.end!.day, widget.end!.hour);

      return PickerDate.isInRange(_nowDate, start, end) ? index.toString().padLeft(2, '0') + '时' : null;
    }
    return null;
  }

  changeMinute(index) {
    if (index == minuteIndex) return;

    setState(() {
      minuteIndex = index;
    });
  }

  String? stringIndexByMinute(int index) {
    if (index >= 0 && index < 60) {
      DateTime _nowDate = DateTime(now.year + yearIndex, monthIndex + 1, dayIndex + 1, hourIndex, index);
      DateTime? start = widget.start == null
          ? null
          : DateTime(widget.start!.year, widget.start!.month, widget.start!.day, widget.start!.hour, widget.start!.minute);
      DateTime? end =
      widget.end == null ? null : DateTime(widget.end!.year, widget.end!.month, widget.end!.day, widget.end!.hour, widget.end!.minute);

      return PickerDate.isInRange(_nowDate, start, end) ? index.toString().padLeft(2, '0') + '分' : null;
    }
    return null;
  }

  changeSecond(index) {
    if (index == secondIndex) return;

    setState(() {
      secondIndex = index;
    });
  }

  String? stringIndexBySecond(int index) {
    if (index >= 0 && index < 60) {
      DateTime _nowDate = DateTime(now.year + yearIndex, monthIndex + 1, dayIndex + 1, hourIndex, minuteIndex, index);
      DateTime? start = widget.start == null
          ? null
          : DateTime(widget.start!.year, widget.start!.month, widget.start!.day, widget.start!.hour, widget.start!.minute, widget.start!.second);
      DateTime? end = widget.end == null
          ? null
          : DateTime(widget.end!.year, widget.end!.month, widget.end!.day, widget.end!.hour, widget.end!.minute, widget.end!.second);

      return PickerDate.isInRange(_nowDate, start, end) ? index.toString().padLeft(2, '0') + '秒' : null;
    }
    return null;
  }

  String? stringIndexByHalf(int index) {
    if (index >= 0 && index < 2) {
      DateTime _nowDate = DateTime(now.year + yearIndex, monthIndex + 1, dayIndex + 1, index == 0 ? 6 : 12, 0);

      return index == 0 ? '上午' : '下午';
    }
    return null;
  }

  changeDate(int _) {
    int days = PickerDate.daysInMonth(new DateTime(now.year + yearIndex, monthIndex + 1));
    DateTime date = new DateTime(now.year + yearIndex, monthIndex + 1, dayIndex + 1, hourIndex, minuteIndex, secondIndex);
    setState(() {

    });

    /// 加入定时器，方便取消
    /// 防止触发多次
    timer?.cancel();
    timer = Timer(Duration(milliseconds: 5), () {
      if (_last != date && dayIndex < days && !_isScroll) {
        _last = date;
        widget.onChange?.call(date);
      }
    });
  }

  changeHalf(int index) {
    changeHour(index == 0 ? 6 : 12);
    setState(() {
      halfIndex = index;
    });
  }

  onConfirm() {
    if (widget.mode != DatePickerMode.full) {
      secondIndex = 0;
    }
    DateTime date = new DateTime(now.year + yearIndex, monthIndex + 1, dayIndex + 1, hourIndex, minuteIndex, secondIndex);
    widget.onConfirm?.call(date);
    Navigator.of(context).pop();
  }

  Widget _renderColumnView({ValueKey? key,
    StringAtIndexCallBack? stringAtIndexCB,
    FixedExtentScrollController? scrollController,
    ValueChanged<int>? selectedChangedWhenScrolling,
    ValueChanged<int>? selectedChangedWhenScrollEnd}) {
    int selectedItemIndex = 0;
    return Expanded(
      // flex: layoutProportion,
      child: Container(
          padding: padding,
          height: widget.pickerHeight, // theme.containerHeight
          // decoration: BoxDecoration(color: Colors.white),
          child: NotificationListener(
              onNotification: (ScrollNotification notification) {
                if (notification.depth == 0 &&
                    selectedChangedWhenScrollEnd != null &&
                    notification is ScrollEndNotification &&
                    notification.metrics is FixedExtentMetrics) {
                  final FixedExtentMetrics metrics = notification.metrics as FixedExtentMetrics;
                  final int currentItemIndex = metrics.itemIndex;
                  selectedChangedWhenScrollEnd(currentItemIndex);
                }
                return false;
              },
              child: ListWheelScrollView.useDelegate(
                key: key,
                // backgroundColor: widget.background ?? Colors.white,
                controller: scrollController,
                itemExtent: widget.itemHeight,
                // theme.itemHeight,
                onSelectedItemChanged: (int index) {
                  selectedItemIndex = index;
                  selectedChangedWhenScrolling?.call(index);
                  setState(() {

                  });
                },
                useMagnifier: true,
                magnification: widget.magnification,
                squeeze: widget.squeeze,
                offAxisFraction: widget.offAxisFraction,
                // selectionOverlay: null,
                // childCount: childCount,
                childDelegate: ListWheelChildBuilderDelegate(builder: (BuildContext context, int index) {
                  final content = stringAtIndexCB?.call(index);
                  if (content == null) {
                    return null;
                  }
                  return Container(
                    height: widget.itemHeight, // theme.itemHeight,
                    alignment: Alignment.center,
                    child: Text(
                      content,
                      style: TextStyle(fontSize: 16).copyWith(color: index != selectedItemIndex ? Colors.grey : (widget.color ?? Color(0xFF000046))),
                      // theme.itemStyle,
                      textAlign: TextAlign.start,
                    ),
                  );
                }),))),
    );
  }

  Widget renderHeader() {
    if (widget.header != null) return widget.header!;

    return Row(
      children: <Widget>[
        TextButton(
          child: Text(
            '取消',
            style: TextStyle(fontSize: 14, color: Colors.grey),
          ),
          onPressed: () {
            widget.onCancel?.call();
            Navigator.of(context).pop();
          },
        ),
        if (widget.title != null)
          Expanded(
              child: Center(
                child: widget.title,
              )),
        TextButton(
          child: Text(
            '确认',
            style: TextStyle(fontSize: 14, color: Colors.blueAccent),
          ),
          onPressed: () {
            onConfirm();
          },
        ),
      ],
      mainAxisAlignment: MainAxisAlignment.spaceBetween,
    );
  }

  Widget renderSheet(BuildContext context) {
    return Row(
      children: <Widget>[
        if (!widget.hideYear)
          _renderColumnView(
            scrollController: yearScrollCtrl,
            selectedChangedWhenScrollEnd: changeDate,
            selectedChangedWhenScrolling: changeYear,
            stringAtIndexCB: stringIndexByYear,
          ),
        if (!widget.hideMonth)
          _renderColumnView(
            scrollController: monthScrollCtrl,
            selectedChangedWhenScrollEnd: changeDate,
            selectedChangedWhenScrolling: changeMonth,
            stringAtIndexCB: stringIndexByMonth,
          ),
        if (!widget.hideDay)
          _renderColumnView(
            scrollController: dayScrollCtrl,
            selectedChangedWhenScrollEnd: changeDate,
            selectedChangedWhenScrolling: changeDay,
            stringAtIndexCB: stringIndexByDay,
          ),
        if (!widget.hideTime)
          _renderColumnView(
            scrollController: hourScrollCtrl,
            selectedChangedWhenScrollEnd: changeDate,
            selectedChangedWhenScrolling: changeHour,
            stringAtIndexCB: stringIndexByHour,
          ),
        if (!widget.hideTime)
          _renderColumnView(
            scrollController: minuteScrollCtrl,
            selectedChangedWhenScrollEnd: changeDate,
            selectedChangedWhenScrolling: changeMinute,
            stringAtIndexCB: stringIndexByMinute,
          ),
        if (!widget.hideSecond)
          _renderColumnView(
            scrollController: secondScrollCtrl,
            selectedChangedWhenScrollEnd: changeDate,
            selectedChangedWhenScrolling: changeSecond,
            stringAtIndexCB: stringIndexBySecond,
          ),
        if (!widget.hideHalf)
          _renderColumnView(
            scrollController: halfScrollCtrl,
            selectedChangedWhenScrollEnd: changeHalf,
            selectedChangedWhenScrolling: changeHalf,
            stringAtIndexCB: stringIndexByHalf,
          ),
      ],
    );
  }

  @override
  Widget build(BuildContext context) {
    bool isShowHeader = widget.isShowHeader || widget.header != null;
    return Container(
      // color: widget.color ?? Colors.white,
      height: widget.pickerHeight + (isShowHeader ? headerHeight : 0),
      child: Column(
        children: <Widget>[
          if (isShowHeader) renderHeader(),
          renderSheet(context),
        ],
      ),
    );
  }
}
